package cn.demoncat.util.pay.yeepay;

import cn.demoncat.util.exception.BizErrorRuntimeException;
import cn.demoncat.util.exception.RestRuntimeException;
import cn.demoncat.util.io.ImgUtil;
import cn.demoncat.util.io.IoUtil;
import cn.demoncat.util.lang.*;
import cn.demoncat.util.lang.constant.StatusConstant;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.entity.ListStr;
import cn.demoncat.util.lang.entity.MapSs;
import cn.demoncat.util.pay.constant.PayMsgs;
import cn.demoncat.util.pay.constant.TradeType;
import cn.demoncat.util.pay.yeepay.config.YeepayProperties;
import cn.demoncat.util.pay.yeepay.constant.YeepayApi;
import cn.demoncat.util.pay.yeepay.constant.YeepayConstant;
import cn.demoncat.util.pay.yeepay.constant.YeepayLogs;
import cn.demoncat.util.pay.yeepay.entity.*;
import cn.demoncat.util.web.HttpClientUtil;
import com.yeepay.g3.sdk.yop.client.YopRequest;
import com.yeepay.g3.sdk.yop.client.YopRsaClient;
import com.yeepay.g3.sdk.yop.encrypt.CertTypeEnum;
import com.yeepay.g3.sdk.yop.encrypt.DigestAlgEnum;
import com.yeepay.g3.sdk.yop.encrypt.DigitalEnvelopeDTO;
import com.yeepay.g3.sdk.yop.encrypt.DigitalSignatureDTO;
import com.yeepay.g3.sdk.yop.utils.DigitalEnvelopeUtils;
import com.yeepay.g3.sdk.yop.utils.InternalConfig;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.function.Function;

/**
 * 易宝支付
 *
 * 文档：https://open.yeepay.com/docs/v2/products/ptssfk/index.html
 * 控制台：https://mp.yeepay.com/auth/signin
 *
 * @author 延晓磊
 * @since 2020年10月15日
 */
@Component
public class YeepayUtil {

    @Resource
    private YeepayProperties yeepayProperties;

    private static final Logger LOG = LoggerFactory.getLogger(YeepayUtil.class);


    /**
     * 计算手续费
     *
     * 注：因为用户支付渠道不同（易宝快捷支付、银联支付），实际手续费不同，应以易宝返回结果为准
     *
     * @param amount    金额
     * @param rate      比例：0.25% == 0.0025
     *
     * @return 2位小数，四舍五入
     *
     * @author 延晓磊
     * @since 2021年03月05日
     */
    public static BigDecimal computeCharge(BigDecimal amount, BigDecimal rate){
        return DecimalUtil.isGt(rate) ? DecimalUtil.toRound(amount, 2) : BigDecimal.ZERO;
    }

    /**
     * POST请求
     *
     * @param api
     * @param request
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月15日
     */
    public YeepayResult post(YeepayApi api, YopRequest request){
        return request(api, request, 2);
    }

    /**
     * GET请求
     *
     * @param api
     * @param request
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月15日
     */
    public YeepayResult get(YeepayApi api, YopRequest request){
        return request(api, request, 1);
    }

    /**
     * UPLOAD请求
     *
     * @param api
     * @param request
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月15日
     */
    public YeepayResult upload(YeepayApi api, YopRequest request){
        return request(api, request, 3);
    }

    /**
     * 普通商户入网申请
     *
     * 1、帮助商户（个体工商户、企业、单位）与易宝支付签约，以对公账户开通结算账户
     * 2、申请流程：OCR机器识别 > 发送短信和邮件给联系人来确认身份 > 人工审核 > 发送通知 > [如果有错误，就重新申请] > 用户在易宝网站签约 > 产品开通
     * 3、支付时，根据平台和易宝签约的方式和费率收取手续费
     * 4、退款、分账（收取服务费、结算货款）时不收费
     * 5、普通商户需要和易宝签约，可以直接向用户收款，也可以接收平台分账。
     *
     * @param param
     *
     * @return {applicationNo申请单号，applicationStatus申请状态}
     *
     * @author 延晓磊
     * @since 2020年10月15日
     */
    public MerchantRegisterResult merchantRegister(MerchantRegisterParam param){
        // 上传营业执照
        param.setLicenceUrl(merchantUpload(param.getLicenceUrl(),2560));
        // 上传身份证
        param.setLegalLicenceFrontUrl(merchantUpload(param.getLegalLicenceFrontUrl(),1024));
        param.setLegalLicenceBackUrl(merchantUpload(param.getLegalLicenceBackUrl(),1024));
        // 上传开户许可证
        param.setOpenAccountLicenceUrl(merchantUpload(param.getOpenAccountLicenceUrl(),1024));
        // 请求信息
        YopRequest request = new YopRequest();
        // 入网请求号：0-64
        request.addParam("requestNo", param.getRequestNo());
        // 商户主体信息：签约类型，统一社会信用代码，营业执照，商户名称，商户简称
        request.addParam("merchantSubjectInfo", JsonUtil.toStr(MapSs.getInstance("signType", StatusConstant.Common.isYes(param.getSignType()) ? "ENTERPRISE":"INDIVIDUAL","licenceNo",param.getLicenceNo(),"licenceUrl", param.getLicenceUrl(),"signName", param.getSignName(),"shortName", param.getShortName(),"openAccountLicenceNo",param.getOpenAccountLicenceNo(),"openAccountLicenceUrl",param.getOpenAccountLicenceUrl())));
        // 商户法人信息：姓名，证件类型，证件号码，身份证正反面
        request.addParam("merchantCorporationInfo", JsonUtil.toStr(MapSs.getInstance("legalName", param.getLegalName(),"legalLicenceType","ID_CARD","legalLicenceNo", param.getLegalLicenceNo(),"legalLicenceFrontUrl", param.getLegalLicenceFrontUrl(),"legalLicenceBackUrl", param.getLegalLicenceBackUrl())));
        // 商户联系人信息：姓名，身份证号，手机号，邮箱
        request.addParam("merchantContactInfo", JsonUtil.toStr(MapSs.getInstance("contactName", param.getContactName(),"contactLicenceNo", param.getContactLicenceNo(),"contactMobile", param.getContactMobile(),"contactEmail", param.getContactEmail())));
        // 经营地址：省市县编码，街道地址
        request.addParam("businessAddressInfo", JsonUtil.toStr(MapSs.getInstance("province", AreaUtil.getProvince(param.getAreaId()),"city",AreaUtil.getCity(param.getAreaId()),"district",param.getAreaId(),"address",param.getAddress())));
        // 结算账户信息：结算到银行，银行账户类型，银行卡号，银行编码
        request.addParam("settlementAccountInfo", JsonUtil.toStr(MapSs.getInstance("settlementDirection", "BANKCARD","bankAccountType", "ENTERPRISE_ACCOUNT","bankCardNo",param.getBankCardNo(),"bankCode",param.getBankCode())));
        // 开通产品信息
        request.addParam("productInfo", yeepayProperties.getProduct());
        // 回调地址
        request.addParam("notifyUrl", yeepayProperties.getMerchantRegisterNotifyUrl());
        // 发起请求
        return MerchantRegisterResult.build(post(YeepayApi.MERCHANT_REGISTER, request));
    }

    /**
     * 商户入网进度查询
     *
     * @param requestNo
     *
     * @return {applicationNo申请单号，applicationStatus申请状态，merchantNo商户编号，auditOpinion审批意见}
     *
     * @author 延晓磊
     * @since 2020年10月15日
     */
    public MerchantRegisterQueryResult merchantRegisterQuery(String requestNo){
        return MerchantRegisterQueryResult.build(get(YeepayApi.MERCHANT_REGISTER_QUERY, new YopRequest().addParam("requestNo", requestNo)));
    }

    /**
     * 商户入网文件上传
     *
     * @param url   原图地址
     * @param limit 尺寸限制（KB）：营业执照2.5M，身份证1M，银行卡1M
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月15日
     */
    public String merchantUpload(String url, int limit){
        // 下载文件
        ByteArrayInputStream input = IoUtil.getInputStream(HttpClientUtil.doGetBytes(url));
        // 压缩
        if (limit > 0) {
            limit *= 1024;
            if (limit < input.available()) {
                ByteArrayOutputStream out = IoUtil.getOutputStream();
                ImgUtil.zoom(input, out, limit);
                input = IoUtil.getInputStream(out.toByteArray());
            }
        }
        // 上传
        return upload(YeepayApi.MERCHANT_UPLOAD, new YopRequest().addFile("merQual", input)).check("returnCode", "returnMsg", "REG00000").getMap().get("merQualUrl").toString();
    }

    /**
     * 交易对账文件下载
     *
     * 注：每天上午10点生成昨天的对账文件，昨天无交易则不生成对账文件
     *
     * @param date   账单日：2017-09-09
     * @return 对账单，没有对账单返回null
     *
     * @author 延晓磊
     * @since 2020年10月20日
     */
    public TradeBillResult tradeBillDownload(String date){
        // 下载账单：商户编号，对账日期，账单类型
        YeepayResult yeepayResult = get(YeepayApi.TRADE_BILL_DOWNLOAD, new YopRequest().addParam("merchantNo", yeepayProperties.getPlatformNo()).addParam("dayString", date).addParam("dataType", "merchant"));
        if (!yeepayResult.getResponse().isSuccess()) {
            LOG.warn("请求易宝支付错误：未查询到 {} 的对账文件", date);
            return null;
        }
        String str = IoUtil.readToStr(yeepayResult.getStream());
        // 对账单
        TradeBillResult result = new TradeBillResult();
        // 去除""
        str = StringUtils.remove(str, '"');
        // 切割行
        List<String> list = ListStr.getInstance(StringUtil.splitLine(str));
        // line 1
        String[] line = StringUtil.split(list.get(0));
        result.setDate(line[1]);
        // line 2
        line = StringUtil.split(list.get(1));
        result.setPayAmount(DecimalUtil.valueOfZero(line[1]));
        result.setRefundAmount(DecimalUtil.valueOfZero(line[3]));
        // line 3
        line = StringUtil.split(list.get(2));
        result.setPayNum(LongUtil.valueOfZero(line[1]));
        result.setRefundNum(LongUtil.valueOfZero(line[3]));
        // line 4
        line = StringUtil.split(list.get(3));
        result.setPayCharge(DecimalUtil.valueOfZero(line[1]));
        result.setRefundCharge(DecimalUtil.valueOfZero(line[3]));
        // line 6+
        result.setDetails(new ArrayList<>());
        TradeBillResultDetail detail;
        for (int i = 5; i < list.size(); i++) {
            line = StringUtil.split(list.get(i));
            detail = new TradeBillResultDetail();
            detail.setTradeTime(line[0]);
            detail.setAccountTime(line[1]);
            detail.setOrderId(line[2]);
            detail.setType(TradeType.check("交易".equals(line[3])));
            detail.setAmount(DecimalUtil.valueOf(line[4]));
            detail.setCharge(DecimalUtil.valueOf(line[5]));
            detail.setMemo(line[8]);
            detail.setUniqueOrderNo(line[9]);
            detail.setUniqueRefundNo(line[11]);
            result.getDetails().add(detail);
        }
        return result;
    }

    /**
     * 交易下单
     *
     * 1、先调用本接口生成预支付订单，易宝返回支付授权 token 后，再使用 token 调起标准收银台支付
     * 2、支付时会根据平台与易宝的签约收取手续费
     * 3、用户支付到平台账户（因为小微不能收款、聚合支付不能合单，所以统一由平台收款），平台再根据订单（合单）情况进行分账
     * 4、如果不分账，付款后D1转到平台银行卡，交易完成（不存在押金，不能退款）
     * 5、如果分账，可以在分账前退款，可以分账（分账后D1转到目标商户/平台的银行卡），可以完结分账（完结后D1转到平台银行卡，交易完成）
     *
     * @param param
     *
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月19日
     */
    public TradeOrderResult tradeOrder(TradeOrderParam param){
        // 请求信息
        YopRequest request = new YopRequest();
        // 发起方商户编号（平台商户）
        request.addParam("parentMerchantNo", yeepayProperties.getPlatformNo());
        // 收款商户编号（因为小微不能收款、聚合支付不能合单，所以统一由平台收款，然后分账给商家）
        request.addParam("merchantNo", yeepayProperties.getPlatformNo());
        /*
          订单编号
          1、相同订单编号可以提交多次，但只能创建1个支付订单
          2、有效期内下单，将返回相同的支付订单，过期时间不变更
          3、过期下单，返回错误"该订单不符合重复下单要求||下单时间错误"
         */
        request.addParam("orderId", param.getOrderId());
        // 订单金额（元）
        request.addParam("orderAmount", param.getOrderAmount());
        // 订单描述/商品信息
        request.addParam("goodsName", param.getGoodsName());
        /*
          是否分账
          1：分账 - 分账后D1转账到目标商户的银行卡
          0：不分账 - 支付后D1转账到平台银行卡
         */
        if (StatusConstant.Common.isYes(param.getFundProcessType())) {
            request.addParam("fundProcessType", YeepayConstant.FUND_PROCESS);
        }
        /*
          过期时间
          1、默认2小时，最长30天，格式为YYYY-MM-dd HH:mm:ss
          2、未过期：相同订单号，总是返回相同的支付订单，过期时间不变更
          3、已过期：相同订单号，不能再次下单
         */
        if (param.getExpiredTime() != null) {
            request.addParam("expiredTime", LocalDateUtil.toStr(param.getExpiredTime()));
        }
        // 支付回调URL
        request.addParam("notifyUrl", yeepayProperties.getPayNotifyUrl());
        // 支付成功后的重定向地址（可以为空，标准收银台的订单有效）
        request.addParam("redirectUrl", param.getRedirectUrl());
        // 对账备注（部署环境）
        request.addParam("memo", param.getMemo());
        // 发起请求
        return TradeOrderResult.build(post(YeepayApi.TRADE_ORDER, request));
    }

    /**
     * 交易订单查询
     *
     * @param orderId   订单编号
     *
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月19日
     */
    public TradeOrderQueryResult tradeOrderQuery(String orderId){
        // 请求信息
        YopRequest request = new YopRequest();
        // 发起方商户编号（平台商户）
        request.addParam("parentMerchantNo", yeepayProperties.getPlatformNo());
        // 收款商户编号（因为小微不能收款、聚合支付不能合单，所以统一由平台收款，然后分账给商家）
        request.addParam("merchantNo", yeepayProperties.getPlatformNo());
        // 订单编号
        request.addParam("orderId", orderId);
        // 发起请求
        return TradeOrderQueryResult.build(get(YeepayApi.TRADE_ORDER_QUERY, request));
    }

    /**
     * 关闭（未支付的）交易订单
     *
     * @param orderId           交易订单编号
     * @param uniqueOrderNo     易宝收款订单号
     *
     * @return 关闭成功true，关闭失败（已支付）false
     *
     * @author 延晓磊
     * @since 2020年10月19日
     */
    public boolean tradeOrderClose(String orderId, String uniqueOrderNo){
        // 请求信息
        YopRequest request = new YopRequest();
        // 发起方商户编号（平台商户）
        request.addParam("parentMerchantNo", yeepayProperties.getPlatformNo());
        // 收款商户编号（因为小微不能收款、聚合支付不能合单，所以统一由平台收款，然后分账给商家）
        request.addParam("merchantNo", yeepayProperties.getPlatformNo());
        // 交易订单编号
        request.addParam("orderId", orderId);
        // 易宝收款订单号
        request.addParam("uniqueOrderNo", uniqueOrderNo);
        // 发起请求
        try {
            post(YeepayApi.TRADE_ORDER_CLOSE, request).check(null,null,"OPR00000");
            return true;
        }catch (Exception e){
            return false;
        }
    }

    /**
     * 申请退款
     *
     * 1、将支付款退还给付款方
     * 2、支持多次退款，需要提交原支付订单的商户订单号和不同的退款单号，总退款金额不能超过用户实际支付金额
     * 3、退款请求成功之后，按退款规则将退款金额原路退回
     * 4、交易订单状态为“SUCCESS”时才能发起退款
     * 5、退款不收取费用
     *
     * @param param
     *
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月19日
     */
    public TradeRefundResult tradeRefund(TradeRefundParam param){
        // 请求信息
        YopRequest request = new YopRequest();
        // 发起方商户编号（平台商户）
        request.addParam("parentMerchantNo", yeepayProperties.getPlatformNo());
        // 收款商户编号（因为小微不能收款、聚合支付不能合单，所以统一由平台收款，然后分账给商家）
        request.addParam("merchantNo", yeepayProperties.getPlatformNo());
        // 订单编号（对应交易订单）
        request.addParam("orderId", param.getOrderId());
        // 易宝收款订单号（对应交易订单）
        request.addParam("uniqueOrderNo", param.getUniqueOrderNo());
        // 退款申请号
        request.addParam("refundRequestId", param.getRefundRequestId());
        // 退款金额（元）
        request.addParam("refundAmount", param.getRefundAmount());
        // 退款原因
        request.addParam("description", param.getDescription());
        // 退款回调URL
        request.addParam("notifyUrl", yeepayProperties.getRefundNotifyUrl());
        // 对账备注（部署环境）
        request.addParam("memo", param.getMemo());
        // 发起请求
        return TradeRefundResult.build(post(YeepayApi.TRADE_REFUND, request));
    }

    /**
     * 退款状态查询
     *
     * @param orderId   交易订单编号
     * @param refundRequestId 退款订单号
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月19日
     */
    public TradeRefundQueryResult tradeRefundQuery(String orderId, String refundRequestId){
        // 请求信息
        YopRequest request = new YopRequest();
        // 发起方商户编号（平台商户）
        request.addParam("parentMerchantNo", yeepayProperties.getPlatformNo());
        // 收款商户编号（因为小微不能收款、聚合支付不能合单，所以统一由平台收款，然后分账给商家）
        request.addParam("merchantNo", yeepayProperties.getPlatformNo());
        // 交易订单编号
        request.addParam("orderId", orderId);
        // 退款订单号
        request.addParam("refundRequestId", refundRequestId);
        // 发起请求
        return TradeRefundQueryResult.build(get(YeepayApi.TRADE_REFUND_QUERY, request));
    }

    /**
     * 申请分账
     *
     * 1、订单支付成功后，发起分账请求，将收款资金分给平台（佣金）、商户（货款）
     * 2、一次分账请求中，有任意一个分账接收方分账失败，则这次分账请求的全部分账明细处理均会失败。
     * 3、支持多次分账，分账不收取费用。
     * 4、分账收款在D1转账到银行卡，多笔收款会进行合并，收款备注为"易宝支付-商户简称-流水号"。
     *
     * @param param
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月19日
     */
    public void divideApply(DivideApplyParam param){
        // 请求信息
        YopRequest request = new YopRequest();
        // 交易发起方商编
        request.addParam("parentMerchantNo", yeepayProperties.getPlatformNo());
        // 收款商户编号（因为小微不能收款、聚合支付不能合单，所以统一由平台收款，然后分账给商家）
        request.addParam("merchantNo", yeepayProperties.getPlatformNo());
        // 订单编号
        request.addParam("orderId", param.getOrderId());
        // 易宝收款订单号
        request.addParam("uniqueOrderNo", param.getUniqueOrderNo());
        // 分账请求号
        request.addParam("divideRequestId", param.getDivideRequestId());
        // 分账详情
        request.addParam("divideDetail", param.getDivideDetail());
        // 发起请求
        post(YeepayApi.DIVIDE_APPLY, request).check(null,null,"OPR00000");
    }

    /**
     * 完结分账
     *
     * 1、将订单的剩余金额转给平台商户，完成交易订单
     * 2、可分账金额为0时会报错
     *
     * @param orderId   交易订单编号
     * @param uniqueOrderNo  易宝收款订单号
     * @param divideRequestId 分账请求号（申请分账、完结分账应使用不同的商户分账请求号）
     * @return 分账金额
     *
     * @author 延晓磊
     * @since 2020年10月19日
     */
    public BigDecimal divideComplete(String orderId, String uniqueOrderNo, String divideRequestId){
        // 请求信息
        YopRequest request = new YopRequest();
        // 发起方商户编号（平台商户）
        request.addParam("parentMerchantNo", yeepayProperties.getPlatformNo());
        // 收款商户编号（因为小微不能收款、聚合支付不能合单，所以统一由平台收款，然后分账给商家）
        request.addParam("merchantNo", yeepayProperties.getPlatformNo());
        // 交易订单编号
        request.addParam("orderId", orderId);
        // 易宝收款订单号
        request.addParam("uniqueOrderNo", uniqueOrderNo);
        // 分账请求号（申请分账、完结分账应使用不同的商户分账请求号，同一请求号视为一次请求）
        request.addParam("divideRequestId", divideRequestId);
        // 发起请求
        YeepayResult result = post(YeepayApi.DIVIDE_COMPLETE, request);
        try {
           return result.check(null, null, "OPR00000").getMap().getBigDecimal("amount");
        }catch (Exception e){
           // 业务错误
           return BigDecimal.ZERO;
        }
    }

    /**
     * 商户入驻通知
     *
     * 通知参数：response
     * 通知重试：成功返回"SUCCESS"，否则间隔5,5,20,270,600,900,1800,3600,14400秒通知9次
     *
     * @param request   易宝请求
     * @param consumer  通知处理器：成功返回true，失败返回false（让再次通知）
     * @return 返回易宝的结果：成功 SUCCESS，错误 ERROR
     *
     * @author 延晓磊
     * @since 2020年10月20日
     */
    public String notifyMerchantRegister(HttpServletRequest request, Function<MerchantRegisterNotify,Boolean> consumer){
        // 接收
        String response = request.getParameter("response");
        String notify = notify(YeepayApi.NOTIFY_MERCHANT_REGISTER, response);
        // 解析
        MerchantRegisterNotify data = JsonUtil.parse(notify, MerchantRegisterNotify.class);
        // 处理
        return notifyConsumer(YeepayApi.NOTIFY_MERCHANT_REGISTER, notify, consumer, data);
    }

    /**
     * 支付成功通知
     *
     * 通知时机：支付成功
     * 通知参数：response
     * 通知重试：成功返回"SUCCESS"，否则间隔5,5,20,270,600,900,1800,3600,14400秒通知9次
     *
     * @param request   易宝请求
     * @param consumer  通知处理器：成功返回true，失败返回false（让再次通知）
     * @return 返回易宝的结果：成功 SUCCESS，错误 ERROR
     *
     * @author 延晓磊
     * @since 2020年10月20日
     */
    public String notifyTradeOrder(HttpServletRequest request, Function<TradeOrderNotify,Boolean> consumer){
        // 接收
        String response = request.getParameter("response");
        String notify = notify(YeepayApi.NOTIFY_TRADE_ORDER, response);
        // 解析
        TradeOrderNotify data = JsonUtil.parse(notify, TradeOrderNotify.class);
        // 处理
        return notifyConsumer(YeepayApi.NOTIFY_TRADE_ORDER, notify, consumer, data);
    }

    /**
     * 退款通知
     *
     * 通知时机：退款完成
     * 通知参数：response
     * 通知重试：成功返回"SUCCESS"，否则间隔5分钟通知3次
     *
     * @param request   易宝请求
     * @param consumer  通知处理器：成功返回true，失败返回false（让再次通知）
     * @return 返回易宝的结果：成功 SUCCESS，错误 ERROR
     *
     * @author 延晓磊
     * @since 2020年10月20日
     */
    public String notifyTradeRefund(HttpServletRequest request, Function<TradeRefundNotify,Boolean> consumer){
        // 接收
        String response = request.getParameter("response");
        String notify = notify(YeepayApi.NOTIFY_TRADE_REFUND, response);
        // 解析
        TradeRefundNotify data = JsonUtil.parse(notify, TradeRefundNotify.class);
        // 消费
        return notifyConsumer(YeepayApi.NOTIFY_TRADE_REFUND, notify, consumer, data);
    }

    /**
     * 支付-标准收银台
     *
     * 场景：H5网站，调用易宝提供的收银台页面
     * 实现：交易下单 + 标准收银台
     *
     * 支持：易宝会判断请求头referer（空或者入网备案域名Domain）
     * 1、浏览器直接访问url
     * 2、后台response.sendRedirect(url)
     * 3、在入网备案域名的网站执行location.href=url
     *
     * 流程：
     * 1、请求交易订单，获取支付token（用于调起标准收银台）
     * 2、请求标准收银台接口，跳转到易宝收银台页面（本方法的终点）
     * 3、支付成功，通知商户支付结果（调用notifyTradeOrder），同时跳转回平台重定向接口（附带参数orderId）
     * 4.1、平台在支付通知接口中，处理订单状态
     * 4.2、平台在支付重定向接口中，根据参数orderId查询订单，再重定向到订单完成页面
     *
     * @param param
     *
     * @return 易宝收银台URL
     *
     * @author 延晓磊
     * @since 2020年10月21日
     */
    public String cashierPay(CashierPayParam param){
        // 日志
        LOG.info(YeepayLogs.REQUEST, YeepayApi.PAGE_CASHIER.getName(), YeepayApi.PAGE_CASHIER.getUrl());
        // 参数
        LinkedHashMap<String,String> map = new LinkedHashMap<>();
        map.put("appKey", yeepayProperties.getAppKey());
        map.put("merchantNo", yeepayProperties.getPlatformNo());
        map.put("token", param.getToken());
        map.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        // 直连参数：设置后直接调用支付工具，不显示易宝收银台
        map.put("directPayType", StringConstant.EMPTY);
        // 卡种：限制信用卡
        map.put("cardType", StringConstant.EMPTY);
        // 用户ID：实现快捷绑卡支付
        map.put("userNo", param.getUserNo());
        map.put("userType", "USER_ID");
        // 微信公众号，支付宝生活号
        map.put("ext", StringConstant.EMPTY);
        // 拼接参数
        String params = MapUtil.toUrl(map);
        // 签名参数
        String sign = sign(params);
        // 拼接（易宝收银台）URL，前端重定向
        return YeepayApi.PAGE_CASHIER.getUrl() + "?sign=" + sign + "&" + params;
    }

    /**
     * 发起请求
     *
     * @param api
     * @param request
     * @param type      1 GET，2 POST，3 UPLOAD
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月16日
     */
    private static YeepayResult request(YeepayApi api, YopRequest request, int type){
        // 日志
        LOG.info(YeepayLogs.REQUEST, api.getName(), api.getUrl());
        LOG.debug("发起请求：api = {}, param = {}", api.getName(), request);
        // 请求
        try {
            switch (type) {
                case 1:
                    return new YeepayResult(api, YopRsaClient.get(api.getUrl(), request));
                case 2:
                    return new YeepayResult(api, YopRsaClient.post(api.getUrl(), request));
                default:
                    return new YeepayResult(api, YopRsaClient.upload(api.getUrl(), request));
            }
        } catch (BizErrorRuntimeException be){
            // 请求错误
            throw be;
        } catch (Exception e) {
            // 请求失败
            LOG.error(YeepayLogs.REQUEST_FAIL, api.getName(), api.getUrl(), e.getMessage(), e);
            throw RestRuntimeException.apiError(PayMsgs.REQUEST_FAIL, e);
        }
    }

    /**
     * 接收通知
     *
     * @param api
     * @param response
     * @return
     *
     * @author 延晓磊
     * @since 2020年10月20日
     */
    private String notify(YeepayApi api, String response){
        LOG.info(YeepayLogs.NOTIFY, api.getName(), api.getUrl());
        // 平台私钥
        PrivateKey privateKey = InternalConfig.getISVPrivateKey(CertTypeEnum.RSA2048);
        // 易宝公钥
        PublicKey publicKey = InternalConfig.getYopPublicKey(CertTypeEnum.RSA2048);
        // 拆封，验签
        DigitalEnvelopeDTO dto = new DigitalEnvelopeDTO();
        dto.setCipherText(response);
        dto = DigitalEnvelopeUtils.decrypt(dto, privateKey, publicKey);
        return dto.getPlainText();
    }

    /**
     * 处理通知
     *
     * @param api
     * @param notify
     * @param consumer
     * @param <T>
     * @return
     */
    private <T>String notifyConsumer(YeepayApi api, String notify, Function<T,Boolean> consumer, T data){
        try {
            // 处理
            return consumer.apply(data) ? YeepayConstant.NOTIFY_SUCCESS_RETURN :YeepayConstant.NOTIFY_ERROR_RETURN;
        }catch (RuntimeException e){
            // 处理失败
            LOG.error(YeepayLogs.NOTIFY_ERROR, api.getName(), api.getUrl(), notify, e);
            throw e;
        }
    }

    /**
     * 参数签名
     * 
     * @param param
     * @return
     * 
     * @author 延晓磊
     * @since 2020年10月21日
     */
    private String sign(String param){
        // 平台私钥
        PrivateKey privateKey = InternalConfig.getISVPrivateKey(CertTypeEnum.RSA2048);
        // 签名
        DigitalSignatureDTO dto = new DigitalSignatureDTO();
        dto.setAppKey(yeepayProperties.getAppKey());
        dto.setCertType(CertTypeEnum.RSA2048);
        dto.setDigestAlg(DigestAlgEnum.SHA256);
        dto.setPlainText(param);
        return DigitalEnvelopeUtils.sign0(dto,privateKey);
    }



}
